﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace MLSharp.Utilities
{
	/// <summary>
	/// A special <see cref="IList"/> that supports callbacks.
	/// </summary>
	/// <remarks>
	/// I created this derived list so that I could add support for callbacks.
	/// These are used so that data sets can validate Ts against
	/// attributes as they are added.
	/// </remarks>
	public class NotifyingList<T> : IList<T>
	{
		#region Private Fields

		/// <summary>
		/// The actual list that holds everything.
		/// </summary>
		private IList<T> mList = new List<T>();

		#endregion

		#region Public Event-related Fields

		/// <summary>
		/// Fired whenever an item is added to the list.
		/// </summary>
		public event Action<T, int> ItemAdded;

		/// <summary>
		/// Fired whenever an item is removed from the list.
		/// </summary>
		public event Action<T, int> ItemRemoved;

		/// <summary>
		/// Event that is raised before a new item is added to the list.
		/// </summary>
		/// <remarks>
		/// Listeners can make changes to the item being added, including
		/// substituting a new item for the original.
		/// </remarks>
		public event RefAction<T, int> ItemAdding;

		#endregion

		#region Private Methods

		/// <summary>
		/// Raises the callback before an item is added.
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index">The index that the item will be inserted
		/// at.</param>
		private void OnItemAdding(ref T item, int index)
		{
			if (ItemAdding != null)
			{
				ItemAdding(ref item, index);
			}
		}

		/// <summary>
		/// Raises the callback when an item has been added.
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index">The index the item was 
		/// added at.</param>
		private void OnItemAdded(T item, int index)
		{
			if (ItemAdded != null)
			{
				ItemAdded(item, index);
			}
		}

		/// <summary>
		/// Raises the callback when an item has been removed.
		/// </summary>
		/// <param name="item"></param>
		/// <param name="index">The old index of the item.</param>
		private void OnItemRemoved(T item, int index)
		{
			if (ItemRemoved != null)
			{
				ItemRemoved(item, index);
			}
		}

		#endregion

		#region IList Members

		///<summary>
		///Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"></see>.
		///</summary>
		///
		///<returns>
		///The index of item if found in the list; otherwise, -1.
		///</returns>
		///
		///<param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"></see>.</param>
		public int IndexOf(T item)
		{
			return mList.IndexOf(item);
		}

		///<summary>
		///Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"></see> at the specified index.
		///</summary>
		///
		///<param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"></see>.</param>
		///<param name="index">The zero-based index at which item should be inserted.</param>
		///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		///<exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		public void Insert(int index, T item)
		{
			OnItemAdded(item, index);

			mList.Insert(index, item);
		}

		///<summary>
		///Removes the <see cref="T:System.Collections.Generic.IList`1"></see> item at the specified index.
		///</summary>
		///
		///<param name="index">The zero-based index of the item to remove.</param>
		///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		///<exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		public void RemoveAt(int index)
		{
			T item = mList[index];

			mList.RemoveAt(index);

			OnItemRemoved(item, index);
		}

		///<summary>
		///Gets or sets the element at the specified index.
		///</summary>
		///
		///<returns>
		///The element at the specified index.
		///</returns>
		///
		///<param name="index">The zero-based index of the element to get or set.</param>
		///<exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see>.</exception>
		///<exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1"></see> is read-only.</exception>
		public T this[int index]
		{
			get { return mList[index]; }
			set { mList[index] = value; }
		}

		///<summary>
		///Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</summary>
		///
		///<param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
		public void Add(T item)
		{
			OnItemAdding(ref item, mList.Count);

			mList.Add(item);

			OnItemAdded(item, mList.Count - 1);
		}

		///<summary>
		///Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</summary>
		///
		///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only. </exception>
		public void Clear()
		{
			//Work backwards through the list.
			for (int i = mList.Count - 1; i >= 0; i--)
			{
				OnItemRemoved(mList[i], i);
			}

			mList.Clear();
		}

		///<summary>
		///Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value.
		///</summary>
		///
		///<returns>
		///true if item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false.
		///</returns>
		///
		///<param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		public bool Contains(T item)
		{
			return mList.Contains(item);
		}

		///<summary>
		///Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
		///</summary>
		///
		///<param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
		///<param name="arrayIndex">The zero-based index in array at which copying begins.</param>
		///<exception cref="T:System.ArgumentOutOfRangeException">arrayIndex is less than 0.</exception>
		///<exception cref="T:System.ArgumentNullException">array is null.</exception>
		///<exception cref="T:System.ArgumentException">array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array.</exception>
		public void CopyTo(T[] array, int arrayIndex)
		{
			mList.CopyTo(array, arrayIndex);
		}

		///<summary>
		///Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</summary>
		///
		///<returns>
		///true if item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>; otherwise, false. This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</returns>
		///
		///<param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.</param>
		///<exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.</exception>
		public bool Remove(T item)
		{
			int index = mList.IndexOf(item);
			if (index < 0)
			{
				return false;
			}

			OnItemRemoved(item, index);

			return mList.Remove(item);
		}

		///<summary>
		///Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</summary>
		///
		///<returns>
		///The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
		///</returns>
		///
		public int Count
		{
			get { return mList.Count; }
		}

		///<summary>
		///Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only.
		///</summary>
		///
		///<returns>
		///true if the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only; otherwise, false.
		///</returns>
		///
		public bool IsReadOnly
		{
			get { return mList.IsReadOnly; }
		}

		///<summary>
		///Returns an enumerator that iterates through the collection.
		///</summary>
		///
		///<returns>
		///A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
		///</returns>
		///<filterpriority>1</filterpriority>
		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			return mList.GetEnumerator();
		}

		///<summary>
		///Returns an enumerator that iterates through a collection.
		///</summary>
		///
		///<returns>
		///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
		///</returns>
		///<filterpriority>2</filterpriority>
		public IEnumerator GetEnumerator()
		{
			return ((IEnumerable<T>) this).GetEnumerator();
		}

		#endregion
	}
}